<?php
/**
 * User object
 *
 * Getting, registrating
 *
 * @package Temply-Account\Objects
 * @license GPL-2.0
 * @author Sergey Dilong
 */

/**
 * User class
 *
 * OOP based object to control all connected with User
 *
 * @package Temply-Account\Objects
 * @license GPL-2.0
 * @author Sergey Dilong
 *
 */
class User
{
    /** @var int $id User ID
     * @see self::getID() Access this field outside the class
     */
    private $id = 0;
    /** @var array $id User data
     * @see self::get() Access this field outside the class
     */
    private $data = [];

    /**
     * Router
     *
     * Get or register User
     *
     * @param int|string $query Query
     *  * *string* - new user (If === `SIGN_UP_MODE` && $o[`signup_proof`])
     *  * *integer* - get a message
     * @param array $o Options
     * * *GET_MODE* [string]
     *      * `login` - get by login
     *      * `id` - get by id
     * * *SIGN_UP_MODE* [bool] - Register new user
     * @see self::ReInitUser() Change options of already inited instance
     * @throws apiException
     * * [101] Login not found
     * * [105] ID not found
     * * [200] DB error
     * * [201] Unfilled required data
     * * [202] Incorrect name
     * * [203] Password is too long
     * * [204] Incorrect login
     */
    public function __construct($query = 0, $o = [])
    {
        // If we've got an ID   ....   or Strict Mode
        if (is_numeric($query) && $o['GET_MODE'] != 'login') {$this->InitUser($query, $o);return;}
        // If we've got a SIGN_UP request
        if ($query === 'SIGN_UP_MODE' && $o['signup_proof'] === true) {$this->SignUp($o);return;}
        // If we've got User's login ... or Strict Mode
        if (is_string($query) && $o['GET_MODE'] != 'id') {$this->InitByLogin($query, $o);return;}
    }

    /**
     * Init user
     *
     * Init user by ID
     *
     * @param int $id ID
     * @param array $o Options
     * * *U_GET* [bool] - apply user-defined rules for displaying User object
     * * *CUSTOM_FIELDS* [string] - force own rules to display the object
     * * *GET_UNSECURE_DATA* [bool] - Gets data such as user password in __protect key
     * @return bool If `true` - success
     * @see self::__construct() Call this function outside the class
     * @see self::ReInitUser() Change options of already inited instance
     * @throws apiException
     * * [105] ID not found
     */
    private function InitUser($id, $o = [])
    {
        global $pdo, $secure;

        // Parse integer
        $id = intval($id);
        // Prepare request
        $ur = $pdo->prepare("SELECT * from `users` WHERE `id` = :uid");
        // Got User's data from DB
        $ur->execute(['uid' => $id]);

        // Check for results
        if (!$ur->rowCount() > 0) { // No results
            throw new apiException(105); // Access Denied
        }

        // Fetch data
        $ur = $ur->fetch();

        // Set required fields by default
        $fm['surname'] = false;
        $fm['online'] = false;

        // Redirect user_fields if needed
        if (is_array($o['CUSTOM_FIELDS'])) {
            $user_f = $o['CUSTOM_FIELDS'];
        }

        // else use user's dedined
        else {
            $user_f = $secure['user_fields'];
        }

        // Fetch required fields
        if ($o['U_GET'] && isset($user_f)) {
            // Explode each
            $epr = explode(',', $user_f);
            foreach ($epr as $v) {
                // Set true for needed
                if ($fm[$v] === false) {
                    $fm[$v] = true;
                }

            }
        }

        if ((isset($user_f) && in_array("all", $epr)) || !$o['U_GET']) {
            // If is not U_GET or there's an all flag - get all
            foreach ($fm as $k => $v) {
                $fm[$k] = true;
            }
        }

        // Get main data
        $this->id = $ur->id;
        $this->data['id'] = $ur->id;
        $this->data['login'] = $ur->login;
        $this->data['name'] = $ur->name;
        if ($fm['surname']) {
            $this->data['surname'] = $ur->surname;
        }
        if ($fm['online']) {
            $this->data['visit'] = $ur->visit;
            $this->data['online'] = (time() - $ur->visit <= 30 ? true : false);
        }

        // Extended, secured data
        if ($o['GET_UNSECURE_DATA']) {
            $this->data['__protect']['password'] = $ur->password; // !!USER'S PASSWORD HASH
        }

        return true;
    }

    /**
     * Re-Init user
     *
     * Change options of already inited object
     *
     * @param $o New options
     * @return array New data (self::data)
     *
     * @throws apiException
     * * [105] ID not found
     */
    public function ReInitUser($o = [])
    {
        // Gives ability to change returned fields in already initialized User
        // Clear data
        $this->data = [];
        // Get new one
        if ($this->id !== false) {
            $this->InitUser($this->id, $o);
        }

        // Return the data
        return $this->data;
    }

    /**
     * Sign Up
     *
     *  This function registers REAL user and calls {@see User::InitUser() Init method}
     * @see self::__construct() Call this method outside the class
     *
     * @param array $d Data
     * * `signup_data` [array]
     *      * `name*` - User's name (Length MIN/MAX: 1/15)
     *      * `login*` - User's login. It must start with a letter, contain only English letters and numbers of any register. (Length MIN/MAX: 5/16)
     *      * `password*` - User's password (Length MIN/MAX: 8/200)
     *      * `surname` - User's surname (Length MIN/MAX: 0/15)
     *
     * @return bool If `true` - success
     * @throws apiException
     * * [200] DB error
     * * [201] Unfilled required data
     * * [202] Incorrect name
     * * [203] Password is too long
     * * [204] Incorrect login
     * */
    private function SignUp($d)
    {
        global $pdo;

        // check if it already filled
        if ($this->id > 0) {
            return;
        }

        // Check all data we got
        $tav = api::required('name, login, password', $d['signup_data']);
        if (!$tav) {
            throw new apiException(201);
            $this->data = false;
            return false;
        };

        // Set surname as string if it's empty
        if (!isset($d['signup_data']['surname'])) {
            $d['signup_data']['surname'] = "";
        }

        // Cut name
        $write['name'] = substr($d['signup_data']['name'], 0, 15);
        $write['surname'] = substr($d['signup_data']['surname'], 0, 15);

        // login
        $write['login'] = substr($d['signup_data']['login'], 0, 15);
        // Only regexp characters. (5-16)
        if (!preg_match('/^[A-Za-z]{1}[A-Za-z0-9]{4,15}$/', $write['login'])) {
            throw new apiException(202);
            $this->data = false;
            return false;
        }

        // Name's min length: 1 symbol
        if (strlen($write['name']) < 1) {
            throw new apiException(202);
            $this->data = false;
            return false;
        }

        // Password
        $check_pass = $d['signup_data']['password'];

        // Max password's length: 200 symbols
        if (strlen($check_pass) > 200) {
            throw new apiException(203);
            $this->data = false;
            return false;
        }

        // Min. length: 8 symbols
        if (strlen($check_pass) < 8) {
            throw new apiException(203);
            $this->data = false;
            return false;
        }
        // Hash password
        $write['password'] = password_hash($check_pass, PASSWORD_DEFAULT);
        if (!password_verify($check_pass, $write['password'])) {
            throw new apiException(200);
            $this->data = false;
            return false;
        }

        // Check existing login
        $cle = $pdo->prepare("SELECT COUNT(*) from `users` WHERE `login` = :login");
        $cle->execute(["login" => $write['login']]);
        $cler = $cle->fetchColumn();
        if ($cler != 0) {
            throw new apiException(204);
            $this->data = false;
            return false;
        }

        // Record reg. time and visit
        $write['regtime'] = time();
        $write['visit'] = $write['regtime'];

        // Insert data to DB
        $v = db::values($write);
        $pr = $pdo->prepare("INSERT into `users` SET $v");
        $pr->execute($write);
        // Get last ID
        $i_id = $pdo->lastInsertId();
        // If ID incorrect - do stuff
        if (!$i_id > 0) {
            throw new apiException(200);
            $this->data = false;
            return false;
        }
        // Init. User
        $this->InitUser($i_id);

        return true;
    }
    /**
     * Get ID
     *
     * Get User's ID
     *
     * @uses self::id to get ID
     * @return int If === `0` - Undefined user
     */
    public function getID()
    {
        // Just returns User's ID
        return $this->id;
    }
    /**
     * Get User data
     *
     * Get all inited User's data
     *
     * @uses self::data to get all the data
     * @return array If array length = 0 - init error
     * @see self::ReInitUser() Update or set new fields in data
     */
    public function get()
    {
        // Just returns User's data
        return $this->data;
    }

    /**
     * Init By login
     *
     * Find user in DB by login and inits by ID
     *
     * @param string $a Login
     * @param array $o Options
     * @see self::InitUser() How user inits
     *
     * @throws apiException
     * * [101] Login not found
     * @return bool If `true` - success
     */
    private function InitByLogin($a, $o = [])
    {
        global $pdo;
        // Chack for login
        $cle = $pdo->prepare("SELECT `id` from `users` WHERE `login` = :login");
        $cle->execute(["login" => $a]);
        $cler = $cle->fetchColumn();

        // If not found - cancel
        if (!$cler > 0) {
            throw new apiException(101);
            $this->data = false;
            return false;
        }

        // Get user
        $this->InitUser($cler, $o);

        return true;
    }

    /**
     * Classes to IDs
     *
     * Converts array of classes to array of IDs
     *
     * @param User[] $q Users array
     *
     * @return int[] Result
     */
    public static function ClassesToIds($q)
    {
        $r = [];
        // Get ID for each user
        foreach ($q as $m) {
            if ($m instanceof User) {
                $r[] = $m->getID();
            }

        }
        return $r;
    }

    /**
     * IDs to Classes
     *
     * Converts array of IDs to array of classes
     *
     * @param int[] $q IDs array
     * @param bool $no_false If `true` - Return errors as false objects
     * @param array $o Options for User object
     * @see self::InitUser() How user inits
     *
     * @return User[] Result
     */
    public static function IdsToClasses($q, $no_false = true, $o = [])
    {
        // Check data
        if (!is_array($q)) {
            return;
        }

        $r = [];
        // Get user for each
        foreach ($q as $k) {
            try {
                // TOOD: Force ID
                $r[] = new User($k, $o);
                // If incorrect ID
            } catch (apiException $e) {
                // If allowed - put false
                if (!$no_false) {
                    $r[] = false;
                }

            }
        }
        return $r;
    }

    /**
     * Classes to Data
     *
     * Extracts data from User classes
     *
     * @see self::InitUser() How data generates
     * @param User[] $q Query
     * @param bool $no_false If `true` - Return errors as false objects
     * @return (array|bool)[]|void Result
     */
    public static function ClassesToData($q, $no_false = true)
    {
        // If incorrect data - return void
        if (!is_array($q)) {
            return;
        }

        $r = [];
        // Convert each
        foreach ($q as $k) {
            // If error
            if (get_class($k) !== "User") {if (!$no_false) {
                $r[] = false;
            }
            } else {
                $r[] = $k->get();
            }

        }
        return $r;
    }
}